;; Title:        Tic-Tac-Toe
;; Description:  Two-player tic-tac-toe in the console
;;               implemented in Common Lisp.
;; Bugs:         Entering a non-number crashes the game. :-)
;;               Does not terminate on a draw.

(defvar board (make-array 9))
(defvar magic-board (make-array 9 :initial-contents '(2 7 6 9 5 1 4 3 8)))
(defvar move 0)
(defvar winner nil)

;; Borrowed from the CL page on Wikipedia.
(defmacro until (test &body body)
  `(do ()
       (,test)
     ,@body))

;; Initialize the board, fill it with underscores
;; to mark empty spaces.
(defun init-board ()
  (loop for i from 0 to 8 do
	(setf (aref board i) "_")))

;; Display the board.
(defun print-board ()
  (format t "~%")
  (loop for i from 0 to 8 do	
	(format t "~a " (aref board i))
	(if (or (= i 2) (= i 5) )
	    (if (= i 8) 
		(format t "~%")
	      (format t "~%----------~%"))
	  (if (not (= i 8))
	      (format t "| ")))))

;; Ask the user for a move, don't 
;; accept until they enter a valid one.
(defun get-move ()
  (format t "~%Please enter a move (0-8): ")
  (let ((mov -1))
    (until (and (>= mov 0) (<= mov 8) (valid-move mov))
	   (setq mov (read)))
    (return-from get-move mov)))

;; Check if the move is legal.
(defun valid-move (x)
  (cond ((string= (aref board x) "_") t) 
	(t nil)))

;; Evaluates to either "X" or "O"
;; depending on the turn.
(defun get-symbol ()
  (cond ((= (mod move 2) 0) 'X)
	(t 'O)))

;; Ask the user to perform a move,
;; then do it and increment the move counter.
(defun do-move ()
  (format t "~%~%It is ~a's turn." (get-symbol))
  (setf (aref board (get-move)) (get-symbol))
  (setq move (+ move 1)))

;; Check if there is a winner using the magic square.
(defun get-winner ()
  (loop for a from 0 to 2 do
	(loop for b from 3 to 5 do
	      (loop for c from 6 to 8 do
		    (if (and (not (string= (aref board a) "_")) 
			     (string= (aref board a) (aref board b)) 
			     (string= (aref board b) (aref board c)) 
			     (= (+ (aref magic-board a) (aref magic-board b) (aref magic-board c)) 15)) 
			(return-from get-winner (aref board a)))))) nil)

;; The game loop, does not quit
;; until someone wins.
(defun game-loop ()
  (until (not (equal winner nil))
	 (print-board)
	 (do-move)
	 (setq winner (get-winner))))

;; Play a game (duh).
(defun play-game ()
  (format t "Welcome to Tic-Tac-Toe!~%")
  (init-board)
  (game-loop)
  (print-board)
  (format t "~%~%The winner is ~a!~%" winner))

;; Let's play!
(play-game)
